import {
	Cartesian3,
	Cartographic,
	Primitive,
	GeometryInstance,
	PolygonGeometry,
	PolygonHierarchy,
	EllipsoidSurfaceAppearance,
	Material,
	Camera,
	ClippingPlaneCollection,
	Plane,
	Matrix4,
	Cesium3DTileset,
	Model,
	Entity,
	ModelGraphics,
	Transforms,
	JulianDate,
	Color,
} from 'cesium'
import * as Cesium from 'cesium'
import { useDayanta } from '@/utils/useAddData'
 
const min_rc = ref(0.8)  	// 最小反射系数
const max_rc = ref(1.0)		// 最大反射系数
const factor = ref(5)			// 视线因子
const frequency = ref(2000)
const animationSpeed = ref(0.01)
const amplitude = ref(5)
const specularIntensity = ref(0.8)
const baseWaterColor = ref('rgba(45, 95, 146, 1.0)')
 
onMounted(async () => {
	const viewer = initCesiumViewer({ baseLayer: useAMapLayer('img'), defaultControls: ['timeline'] })
 
	viewer.scene.sun.glowFactor = 10
	const tileset = await useDayanta(viewer)
 
	viewer.entities.add({
		position: Cartesian3.fromDegrees(108.959, 34.219, 30),
		model: {
			uri: '../models/CesiumAir/Cesium_Air.glb',
		},
	})
 
	viewer.entities.add({
		position: Cartesian3.fromDegrees(108.9593, 34.2195, 50),
		model: {
			uri: '../models/CesiumBalloon/CesiumBalloon.glb',
		},
	})
 
	const modelEntity = viewer.entities.add({
		position: Cartesian3.fromDegrees(108.959, 34.2192, 10),
		model: {
			uri: '../models/WoodTower/Wood_Tower.glb',
		},
	})
 
	const model = await Model.fromGltfAsync({
		url: '../models/WoodTower/Wood_Tower.glb',
		modelMatrix: Cesium.Transforms.eastNorthUpToFixedFrame(Cartesian3.fromDegrees(108.9597, 34.2192, 10))
	})
	viewer.scene.primitives.add(model)
 
 
	const positions = Cartesian3.fromDegreesArrayHeights([
		108.955, 34.215, 15,
		108.965, 34.215, 15,
		108.965, 34.225, 15,
		108.955, 34.225, 15
	])
	const waterPlane = new Primitive({
		geometryInstances: new GeometryInstance({
			geometry: new PolygonGeometry({
				polygonHierarchy: new PolygonHierarchy(positions),
				perPositionHeight: true
			})
		}),
		appearance: new EllipsoidSurfaceAppearance({
			material: new Material({
				fabric: {
					type: 'ReflectionWaterPlane',
					uniforms: {
						image: '',
						waterNormal: new Cartesian3(),
						fadeFactor: 1.0,  // 此参数不用调节
						normalMap: '../images/material/water1.jpg',
						fresnelParams: new Cartesian3(0.8, 1.0, 5),
						specularMap: 'czm_defaultImage',
						baseWaterColor: Color.fromCssColorString('rgba(45, 95, 146, 1.0)'),
						blendColor: new Color(),
						frequency: 2000,
						animationSpeed: 0.01,
						amplitude: 5,
						specularIntensity: 0.8,
					},
					source: `
						uniform sampler2D image;
						uniform vec3 waterNormal;
						uniform vec3 fresnelParams;  // x为最小反射系数，y为最大反射系数，z为视线因子
						uniform sampler2D specularMap;
						uniform sampler2D normalMap;
						uniform vec4 baseWaterColor;
						uniform vec4 blendColor;
						uniform float frequency;
						uniform float animationSpeed;
						uniform float amplitude;
						uniform float specularIntensity;
						uniform float fadeFactor;
						czm_material czm_getMaterial(czm_materialInput materialInput) {
							czm_material material = czm_getDefaultMaterial(materialInput);
							float time = czm_frameNumber * animationSpeed;
							// 衰减是与碎片的距离和波的频率的函数
							float fade = max(1.0, (length(materialInput.positionToEyeEC) / 10000000000.0) * frequency 					* fadeFactor);
							float specularMapValue = texture(specularMap, materialInput.st).r;
							// 注意：此时不使用方向运动，只需将角度设置为0.0；
							vec4 noise = czm_getWaterNoise(normalMap, materialInput.st * frequency, time, 0.0);
							vec3 normalTangentSpace = noise.xyz * vec3(1.0, 1.0, (1.0 / amplitude));
							// 当我们远离水面时，正常的扰动逐渐消失
							normalTangentSpace.xy /= fade;
	  					    // 我们接近非水区域（低镜面贴图值）时，尝试淡出正常扰动
							normalTangentSpace = mix(vec3(0.0, 0.0, 50.0), normalTangentSpace, specularMapValue);
							normalTangentSpace = normalize(normalTangentSpace);
							// 获取新法向量与垂直于切平面的向量的对齐比率
							float tsPerturbationRatio = clamp(dot(normalTangentSpace, vec3(0.0, 0.0, 1.0)), 0.0, 1.0);
							// 随着高光贴图值的减少，淡化水的效果
							float alpha = mix(blendColor.a, baseWaterColor.a, specularMapValue) * specularMapValue;
							// 基础色是水和非水颜色的混合，基于镜面贴图的值，可能需要一个均匀的混合因子来更好地控制这一点
							vec3 waterColor = mix(blendColor.rgb, baseWaterColor.rgb, specularMapValue);
							// 漫射高光是基于法线的扰动程度
							waterColor += (0.1 * tsPerturbationRatio);
							// 获取水面反射的颜色
							vec2 uv = gl_FragCoord.xy / czm_viewport.zw;  // 获取屏幕uv
							uv.s = (1.0 - uv.s); 			// 镜像翻转
							// 按照法线方向来扰动反射贴图的纹理坐标
							uv += normalTangentSpace.xz / 100.0;
							vec3 reflectColor = texture(image, uv).rgb;
							// 加入菲涅尔现象
							vec3 positionEC = -materialInput.positionToEyeEC;
							vec4 positionWC = czm_inverseView * vec4(positionEC, 1.0);
							vec3 cameraDir = normalize(positionWC.xyz - czm_viewerPositionWC);
							vec3 n = normalize(waterNormal);
							float fresnel = 1.0 - dot(-cameraDir, n);
							float min_rc = fresnelParams.x; 		// 物体最小反射系数
							float max_rc = fresnelParams.y;		 	// 物体最大反射系数
							float f = fresnelParams.z;  				// 视线因子
							fresnel = min_rc + (max_rc - min_rc) * pow(fresnel, f);
							// 菲涅尔值高，代表反射强(更多显示水的反射)，值低则是折射强(也可以理解成更多显示水的本色)
							material.diffuse = mix(waterColor, reflectColor, fresnel);
							material.alpha = mix(alpha, 1.0, fresnel);
							material.normal = normalize(materialInput.tangentToEyeMatrix * normalTangentSpace);
							material.specular = specularIntensity;
							material.shininess = 10.0;
							return material;
						}
					`
				}
			})
		})
	})
	viewer.scene.primitives.add(waterPlane)
 
	const centerPosition = calcCenterPoint(positions)
	const waterNormal = viewer.scene.globe.ellipsoid.geodeticSurfaceNormal(centerPosition)
	// 这里准备了3个不同类型的示例数据，第一个是3dtileset，第二个采用entity加载模型，第三个则使用Model加载
	// 如果一个模型没有部分在水下，那自然就不需要裁剪了
	const models = [tileset, modelEntity, model]
	const clipPlaneCollections = models.map(model => clipAnyModel(model, centerPosition, waterNormal))
 
	// 设置uniforms，以及监听更改
	const uniforms = waterPlane.appearance.material.uniforms
	uniforms.waterNormal = waterNormal
	watchEffect(() => {
		uniforms.fresnelParams = new Cartesian3(min_rc.value, max_rc.value, factor.value)
		uniforms.frequency = frequency.value
		uniforms.animationSpeed = animationSpeed.value
		uniforms.amplitude = amplitude.value
		uniforms.specularIntensity = specularIntensity.value
		uniforms.baseWaterColor = Color.fromCssColorString(baseWaterColor.value)
	})
	// 渲染。该事件的回调函数在每次渲染之前都会被执行，确保在渲染前做一些预处理操作
	viewer.scene.preRender.addEventListener(() => {
		const scene = viewer.scene as any
		// 获取绘制区域的宽高
		const width = scene.context.drawingBufferWidth
		const height = scene.context.drawingBufferHeight
 
		// 创建Framebuffer对象
		const fbo = new (Cesium as any).Framebuffer({
			context: scene.context,
			colorTextures: [
				new (Cesium as any).Texture({
					context: scene.context,
					width: width,
					height: height,
					pixelFormat: Cesium.PixelFormat.RGBA,
				}),
			],
		})
 
		// 获取反射相机
		const reflectCamera = getMirrorCamera(viewer.camera, waterNormal, centerPosition)
		/**
		 * 裁剪面是用来限制渲染区域的平面。在 Cesium 中，裁剪面通常用于控制在渲染时只显示位于某些区域的内容。
		 * 裁剪面可以限制物体只在某个平面的一侧可见
		 * 在这里，开启裁剪面是为了确保反射相机只渲染水面以上的场景部分（而不是渲染整个场景），以模拟水面反射效果
		 */
		// 渲染前开启裁剪面
		clipPlaneCollections.forEach(clipPlaneCollection => {
			if (clipPlaneCollection) clipPlaneCollection.enabled = true
		})
		// 调用 renderToFbo 渲染到FBO
		renderToFbo(fbo, scene, reflectCamera)
		// 渲染后关闭裁剪面，以便恢复到正常的渲染状态，不再限制渲染区域
		clipPlaneCollections.forEach(clipPlaneCollection => {
			if (clipPlaneCollection) clipPlaneCollection.enabled = false
		})
		// 将渲染到 FBO 中的反射纹理应用到水面材质，最终渲染出带有反射效果的水面
		const colorTexture = fbo.getColorTexture(0)
		waterPlane.appearance.material.uniforms.image = colorTexture
	})
 
})
 
/**计算多边形的中心点，如果传递了高度，那么中心点的高程将为指定的高度 */
function calcCenterPoint(positions: Cartesian3[], heigth?: number) {
	const total = new Cartesian3()
	positions.forEach(item => {
		Cartesian3.add(total, item, total)
	})
	let centerPosition = Cartesian3.multiplyByScalar(total, 1 / positions.length, total)
	if (heigth) {
		const centerCarto = Cartographic.fromCartesian(centerPosition)
		const newCarto = new Cartographic(centerCarto.longitude, centerCarto.latitude, heigth)
		centerPosition = Cartographic.toCartesian(newCarto)
	}
	return centerPosition
}
 
/**
* @param camera 原始相机
* @param normal 反射面的法向量
* @param centerPosition 反射面的中心点
*/
function getMirrorCamera(camera: Camera, normal: Cartesian3, centerPosition: Cartesian3) {
	// 场景相机到反射面中心点的向量
	const cameraToCenter = Cartesian3.subtract(centerPosition, camera.position, new Cartesian3())
	const n = -Cartesian3.dot(normal, cameraToCenter)
	const t = Cartesian3.multiplyByScalar(normal, 2 * n, new Cartesian3())
	const reflectCameraPosition = Cartesian3.subtract(camera.position, t, new Cartesian3())
	// 反射向量就是 相机原本方向 - 2倍的方向在法向量的投影
	const ndir = Cartesian3.dot(normal, camera.directionWC)
	const tdir = Cartesian3.multiplyByScalar(normal, 2 * ndir, new Cartesian3())
	const reflectCameraDirection = Cartesian3.subtract(camera.directionWC, tdir, new Cartesian3())
	Cartesian3.normalize(reflectCameraDirection, reflectCameraDirection)
	// up反射后的结果同方向计算一样
	const nup = Cartesian3.dot(normal, camera.upWC)
	const tup = Cartesian3.multiplyByScalar(normal, 2 * nup, new Cartesian3())
	const reflectCameraUp = Cartesian3.subtract(camera.upWC, tup, new Cartesian3())
 
	// 创建反射相机，克隆可以继承原始相机的视锥体参数
	const reflectCamera = (Camera as any).clone(camera) as Camera
	reflectCamera.position = reflectCameraPosition
	reflectCamera.up = reflectCameraUp
	reflectCamera.direction = reflectCameraDirection
	reflectCamera.right = Cartesian3.cross(reflectCameraUp, reflectCameraDirection, new Cartesian3())
 
	return reflectCamera
}
 
 
/**在给定的相机视角下渲染*/
/**
 * 将场景渲染到一个Framebuffer Object (FBO) 中的功能。
 * FBO是一个离屏渲染目标，允许在不直接显示到屏幕上的情况下进行图形渲染，通常用于实现后期处理、纹理生成等效果。
 */
function renderToFbo(fbo: any, scene: any, camera: Camera) {
	const frameState = scene._frameState
	// 1. 禁用地球显示
	scene.globe.show = false
	const context = scene.context
	const us = context.uniformState
  // 2. 保存原始的相机状态
	const preCamera = scene._defaultView.camera
	// 将当前场景的相机设置为传入的相机 camera，这意味着后续的渲染将基于这个相机视角来进行
	scene._defaultView.camera = camera
	const view = scene._defaultView
	scene._view = view
  // 3. 更新场景的帧状态，确保场景信息是最新的
	scene.updateFrameState()
	frameState.passes.render = true
	frameState.passes.postProcess = scene.postProcessStages.hasSelected
	// 4. 设置Tileset渲染的状态
	const renderTilesetPassState = new (Cesium as any).Cesium3DTilePassState({
		pass: (Cesium as any).Cesium3DTilePass.RENDER,
	})
	frameState.tilesetPassState = renderTilesetPassState
  // 5. 设置背景颜色
	let backgroundColor = Cesium.defaultValue(scene.backgroundColor, Cesium.Color.BLACK);
	// if (scene._hdr) {
	// 	backgroundColor = Cesium.Color.clone(backgroundColor, new Color())
	// 	backgroundColor.red = Math.pow(backgroundColor.red, scene.gamma)
	// 	backgroundColor.green = Math.pow(backgroundColor.green, scene.gamma)
	// 	backgroundColor.blue = Math.pow(backgroundColor.blue, scene.gamma)
	// }
	frameState.backgroundColor = backgroundColor
	frameState.atmosphere = scene.atmosphere
	// 6. 更新场景中的雾效（fog effect）。雾效通常在渲染时影响可视距离和景深
	scene.fog.update(frameState)
	us.update(frameState)
 
	// const shadowMap = scene.shadowMap
	// if (Cesium.defined(shadowMap) && shadowMap.enabled) {
	// 	if (!Cesium.defined(scene.light) || scene.light instanceof Cesium.SunLight) {
	// 		Cesium.Cartesian3.negate(us.sunDirectionWC, scene._shadowMapCamera.direction)
	// 	} else {
	// 		Cesium.Cartesian3.clone(scene.light.direction, scene._shadowMapCamera.direction)
	// 	}
	// 	frameState.shadowMaps.push(shadowMap)
	// }
 
	scene._computeCommandList.length = 0
	scene._overlayCommandList.length = 0
  // 7. 配置视口（viewport），指定渲染的区域为整个画布的大小。
	// drawingBufferWidth 和 drawingBufferHeight 是当前画布的尺寸
	const viewport = view.viewport
	viewport.x = 0
	viewport.y = 0
	viewport.width = context.drawingBufferWidth
	viewport.height = context.drawingBufferHeight
  // 8. 设置渲染目标为传入的FBO，这意味着接下来的渲染操作将不会直接显示在屏幕上，而是绘制到这个FBO中
	const passState = view.passState
	// 最终绘制到传入的fbo
	passState.framebuffer = fbo
	// 关闭了混合（blendingEnabled）和剪裁测试（scissorTest）
	passState.blendingEnabled = undefined
	passState.scissorTest = undefined
	passState.viewport = Cesium.BoundingRectangle.clone(viewport, passState.viewport);
 
	// if (Cesium.defined(scene.globe)) {
	// 	scene.globe.beginFrame(frameState)
	// }
  // 9. 更新环境和执行渲染命令
	scene.updateEnvironment();
	scene.updateAndExecuteCommands(passState, backgroundColor)
	// 10. 解析帧缓冲区并恢复设置
	scene.resolveFramebuffers(passState)
	// 表示渲染完成，不再使用FBO作为目标
	passState.framebuffer = undefined
 
	// if (Cesium.defined(scene.globe)) {
	// 	scene.globe.endFrame(frameState)
	// 	if (!scene.globe.tilesLoaded) {
	// 		scene._renderRequested = true
	// 	}
	// }
  // 11. 结束帧并恢复设置
	context.endFrame()
	scene.globe.show = true
	scene._defaultView.camera = preCamera
}
 
 
/** 根据参数裁剪3DTileset, Model, 或者Entity中的model，保留法向量方向上的部分
 * @param m Cesium3DTileset, Model, Entity
 * @param centerPosition  裁剪面的中心点
 * @param normal 裁剪面的法向量
 * @returns 生成的ClippingPlaneCollection，或者entity中没有模型则返回undefined
 */
function clipAnyModel(m: Cesium3DTileset | Model | Entity, centerPosition: Cartesian3, normal: Cartesian3) {
	let transform: Matrix4
	let model: Cesium3DTileset | Model | ModelGraphics
	// tileset有可能进行了一些变换，所以需要将transform乘上modelMatrix
	if (m instanceof Cesium3DTileset) {
		model = m
		transform = Matrix4.multiply(model.modelMatrix, model.root.transform, new Matrix4())
		Matrix4.inverse(transform, transform)
	}
	else if (m instanceof Model) {
		model = m
		transform = Matrix4.inverse(model.modelMatrix, new Matrix4())
	}
	else {
		if (!m.model) return
		transform = Transforms.eastNorthUpToFixedFrame(m.position!.getValue(new JulianDate())!)
		Matrix4.inverse(transform, transform)
		model = m.model
	}
 
	const clippingPlanes = new ClippingPlaneCollection()
	const plane = Plane.fromPointNormal(centerPosition, normal)
	const clipPlane = Plane.transform(plane, transform)
	clippingPlanes.add(clipPlane)
	model.clippingPlanes = clippingPlanes
 
	return clippingPlanes
}
